package com.clear.honghefinance.biz.modules.system.resource.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.clear.honghefinance.biz.constant.RedisKeyConstant;
import com.clear.honghefinance.biz.modules.system.resource.converter.SysResourceConverter;
import com.clear.honghefinance.biz.modules.system.resource.domain.entity.SysResource;
import com.clear.honghefinance.biz.modules.system.member.domain.vo.AdminDTO;
import com.clear.honghefinance.biz.modules.system.resource.domain.dto.CreateSysResourceDTO;
import com.clear.honghefinance.biz.modules.system.resource.domain.dto.DeleteSysResourceDTO;
import com.clear.honghefinance.biz.modules.system.resource.domain.dto.ListSysResourceDTO;
import com.clear.honghefinance.biz.modules.system.resource.domain.dto.UpdateSysResourceDTO;
import com.clear.honghefinance.biz.modules.system.resource.domain.vo.GetSysResourceVO;
import com.clear.honghefinance.biz.modules.system.resource.domain.vo.ListSysResourceVO;
import com.clear.honghefinance.biz.modules.system.resource.mapper.SysResourceMapper;
import com.clear.honghefinance.biz.modules.system.resource.service.ISysResourceService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.clear.honghefinance.common.exception.BizException;
import com.clear.honghefinance.common.service.TokenService;
import com.clear.honghefinance.common.util.DateUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * <p>
 * 系统资源 服务实现类
 * </p>
 *
 * @author 程序员clear
 * @since 2024-08-15
 */
@Service
@Slf4j
public class SysResourceServiceImpl extends ServiceImpl<SysResourceMapper, SysResource> implements ISysResourceService {

    @Resource
    private TokenService<AdminDTO> tokenService;
    @Resource
    private RedissonClient redissonClient;

    /**
     * 创建资源
     *
     * @param dto 创建资源dto
     * @return 成功ture、失败false
     */
    @Override
    public boolean create(CreateSysResourceDTO dto) {
        if (log.isInfoEnabled()) {
            log.info("创建资源中，入参：{}", JSON.toJSONString(dto));
        }
        // 1.转换
        SysResource sysResource = SysResourceConverter.INSTANCE.convertDTOToSysResourceEntity(dto);
        sysResource.initDefault();
        sysResource.setMemberId(tokenService.getThreadLocalUserId());
        sysResource.setUpdateMemberId(tokenService.getThreadLocalUserId());

        RLock rLock = redissonClient.getLock(RedisKeyConstant.CHANGE_RESOURCE_LOCK);
        try {
            rLock.lock();
            // 2.根据资源路径查询资源
            SysResource resource = getResourceByPath(dto.getPath());
            if (Objects.nonNull(resource)) {
                throw new BizException("该资源路径已存在");
            }
            // 3.判断待创建的资源的合法性
            if (dto.getPid() > 0) {  // 创建的是二级资源
                // a.查询上级资源是否存在
                SysResource parentSysResource = getResourceById(dto.getPid());
                if (Objects.isNull(parentSysResource)) {
                    throw new BizException("上级资源不存在");
                }
                // b.判断父级资源的pid，pia != 0 非法（因为目前最多只能创建二级资源）
                if (parentSysResource.getPid() > 0) {
                    throw new BizException("不能创建三级资源");
                }
                // c.校验path合法性
                checkPath(parentSysResource.getPath(), dto.getPath());
            } else {  // 创建的是一级资源
                checkPath(dto.getPath(), null);
            }
            // 4.插入资源
            return this.save(sysResource);
        } catch (Exception e) {
            throw new BizException(e.getMessage(), e);
        } finally {
            rLock.unlock();
        }
    }

    /**
     * 修改资源
     *
     * @param dto dto
     * @return 成功ture、失败false
     */
    @Override
    public boolean update(UpdateSysResourceDTO dto) {
        if (log.isInfoEnabled()) {
            log.info("修改资源中，入参：{}", JSON.toJSONString(dto));
        }
        RLock rLock = redissonClient.getLock(RedisKeyConstant.CHANGE_RESOURCE_LOCK);
        try {
            rLock.lock();
            // 1.根据id获取资源
            SysResource sysResource = getResourceById(dto.getId());
            if (Objects.isNull(sysResource)) {
                throw new BizException("资源不存在");
            }
            if (sysResource.getPid() <= 0) {
                throw new BizException("不能修改一级资源");
            }
            // 2.检查资源路径是否已存在，如果存在且不是当前资源，则抛出异常
            SysResource sysResourceByPath = getResourceByPath(dto.getPath());
            if (Objects.nonNull(sysResourceByPath) && !sysResourceByPath.getId().equals(dto.getId())) {
                // 该path已存在，并且id != dto.id，提示错误
                throw new BizException("该资源路径已存在");
            }
            // 3.获取父资源以判断修改后的资源path合法性
            SysResource parentSysResource = getResourceById(sysResource.getPid());
            // 4.检查资源path合法性
            if (Objects.isNull(parentSysResource)) {
                checkPath(dto.getPath(), null);
            } else {
                checkPath(parentSysResource.getPath(), dto.getPath());
            }
            // 5.修改
            LambdaUpdateWrapper<SysResource> lambdaUpdateWrapper = Wrappers.<SysResource>lambdaUpdate()
                    .eq(SysResource::getId, dto.getId())
                    .set(SysResource::getPath, dto.getPath())
                    .set(SysResource::getName, dto.getName())
                    .set(SysResource::getMemberId, tokenService.getThreadLocalUserId())
                    .set(SysResource::getUpdateTime, DateUtil.getSystemTime());
            return this.update(lambdaUpdateWrapper);
        } catch (Exception e) {
            throw new BizException(e.getMessage(), e);
        } finally {
            rLock.unlock();
        }
    }

    /**
     * 删除资源
     *
     * @param dto dto
     * @return 成功ture、失败false
     */
    @Override
    public boolean delete(DeleteSysResourceDTO dto) {
        if (log.isInfoEnabled()) {
            log.info("删除资源中，入参：{}", JSON.toJSONString(dto));
        }
        // 1.根据id统计子节点数量
        int count = countResourceByPid(dto.getId());  // 传入的如果是父id，count>0
        if (count > 0) {
            throw new BizException("该资源下面还有子资源，不能删除");
        }
        // 2.逻辑删除
        RLock rLock = redissonClient.getLock(RedisKeyConstant.CHANGE_RESOURCE_LOCK);
        try {
            rLock.lock();
            LambdaUpdateWrapper<SysResource> updateWrapper = Wrappers.<SysResource>lambdaUpdate()
                    .eq(SysResource::getId, dto.getId())
                    .set(SysResource::getDelFlag, true)
                    .set(SysResource::getUpdateMemberId, tokenService.getThreadLocalUserId())
                    .set(SysResource::getUpdateTime, DateUtil.getSystemTime());
            return this.update(updateWrapper);
        } catch (Exception e) {
            throw new BizException(e.getMessage(), e);
        } finally {
            rLock.unlock();
        }
    }

    /**
     * 获取资源列表
     *
     * @param dto dto
     * @return 成功ture、失败false
     */
    @Override
    public List<ListSysResourceVO> list(ListSysResourceDTO dto) {
        List<ListSysResourceVO> result = null;
        if (log.isInfoEnabled()) {
            log.info("获取资源列表中，入参：{}", JSON.toJSONString(dto));
        }
        // 1.查询所有资源
        List<SysResource> sysResourceList = this.list(Wrappers.<SysResource>lambdaQuery()
                .eq(SysResource::getDelFlag, false)
                .eq(SysResource::getDisable, false)
                .like(StringUtils.isNotBlank(dto.getName()), SysResource::getName, dto.getName())
                .like(StringUtils.isNotBlank(dto.getPath()), SysResource::getPath, dto.getPath())
        );
        // 2.获取所有父级资源 pid==0 即为父级资源
        List<SysResource> parentResourceList = sysResourceList.stream()
                .filter(p -> p.getPid() == 0).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(parentResourceList)) {
            return Collections.emptyList();
        }
        // 3.将父级资源转换为ListSysResourceVO
        result = SysResourceConverter.INSTANCE.convertSysResourceListToListSysResourceVOList(parentResourceList);
        // 4.遍历父级资源，为他们添加子资源
        for (ListSysResourceVO vo : result) {
            // a.筛选出当前父级资源的所有子资源
            List<SysResource> childResouceList = sysResourceList.stream()
                    .filter(p -> Objects.equals(p.getPid(), vo.getId()))
                    .collect(Collectors.toList());
            if (CollectionUtils.isEmpty(childResouceList)) {
                continue;
            }
            // b.将子资源转换为ListSysResourceVO。并组装到当前父级资源的children中
            vo.setChildren(SysResourceConverter.INSTANCE.convertSysResourceListToListSysResourceVOList(childResouceList));
        }
        return result;
    }

    /**
     * 获取资源明细
     *
     * @param id 资源id
     * @return
     */
    @Override
    public GetSysResourceVO get(Integer id) {
        log.info("获取资源明细中，入参：{}", id);
        // 1.根据id获取资源
        SysResource sysResource = getResourceById(id);
        if (Objects.isNull(sysResource)) {
            throw new BizException("资源不存在");
        }
        GetSysResourceVO result = SysResourceConverter.INSTANCE.convertSysResourceEntityToSysResourceVO(sysResource);
        result.setParentName("无上级资源");
        // 2.设置上级资源名称
        if (sysResource.getPid() > 0) {
            SysResource parentSysResource = getResourceById(sysResource.getPid());
            if (parentSysResource == null) {
                throw new BizException("上级资源不存在");
            }
            result.setParentName(parentSysResource.getName());
        }
        return result;
    }

    /**
     * 根据id查询资源列表
     *
     * @param ids 资源id列表
     * @return
     */
    @Override
    public List<SysResource> listResourceByIds(List<Integer> ids) {
        if (CollectionUtils.isEmpty(ids)) {
            return Collections.emptyList();
        }
        return this.list(Wrappers.<SysResource>lambdaQuery()
                .in(SysResource::getId, ids)
                .eq(SysResource::getDelFlag, false)
        );
    }

    /**
     * 根据pid统计子节点数量
     *
     * @param pid pid
     * @return 子节点数量
     */
    private int countResourceByPid(Integer pid) {
        return this.count(Wrappers.<SysResource>lambdaQuery()
                .eq(SysResource::getPid, pid)
                .eq(SysResource::getDelFlag, false)
                .eq(SysResource::getDisable, false)
        );
    }

    /**
     * 检查path是否合法
     *
     * @param parentPath 父级资源path
     * @param childPath  子资源path
     * @return 合法返回ture、不合法返回false
     */
    private boolean checkPath(String parentPath, String childPath) {
        if (parentPath == null) {
            throw new BizException("父级资源为空！");
        }
        // 检查路径格式
        String parentRegex = "^/[a-zA-Z0-9_*-]+(?:/[a-zA-Z0-9_*-]+)*/\\*+$";
        if (!parentPath.matches(parentRegex)) {
            throw new BizException("父级资源路径格式不正确");
        }
        if (childPath == null) {
            return true;
        }
        // 获取/*之前的内容
        String prefix = parentPath.substring(0, parentPath.lastIndexOf("/") + 1);
        if (!childPath.startsWith(prefix)) {
            throw new BizException("子级资源路径格式不正确");
        }
        String childRegex = "^/[a-zA-Z0-9_*-]+(?:/[a-zA-Z0-9_*-]+)*$";

        if (!childPath.matches(childRegex)) {
            throw new BizException("子级资源路径格式不正确");
        }
        return true;
    }

    /**
     * 根据path获取资源
     *
     * @param path path
     * @return 资源
     */
    private SysResource getResourceByPath(String path) {
        return this.getOne(Wrappers.<SysResource>lambdaQuery()
                .eq(SysResource::getPath, path)
                .eq(SysResource::getDelFlag, false)
                .eq(SysResource::getDisable, false)
        );
    }

    /**
     * 根据id获取资源
     *
     * @param id id
     * @return 资源
     */
    public SysResource getResourceById(int id) {
        return this.getOne(Wrappers.<SysResource>lambdaQuery()
                .eq(SysResource::getId, id)
                .eq(SysResource::getDelFlag, false)
                .eq(SysResource::getDisable, false)
        );
    }
}
